//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
//
using UnityEngine;
using System.Collections;

namespace HUX.Spatial
{
    [RequireComponent(typeof(SolverHandler))]

	/// <summary>
	///   ConstantViewSize solver scales to maintain a constant size relative to the view (currently tied to the Veil)
	/// </summary>
	public class SolverConstantViewSize : Solver
	{
		#region public members
		[Tooltip("The object take up this percent vertically in our view (not technically a percent use 0.5 for 50%)")]
		public float TargetViewPercentV = 0.5f;

		[Tooltip("If the object is closer than MinDistance, the distance used is clamped here")]
		public float MinDistance = 0.5f;
		[Tooltip("If the object is farther than MaxDistance, the distance used is clamped here")]
		public float MaxDistance = 3.5f;

		[Tooltip("Minimum scale value possible (world space scale)")]
		public float MinScale = 0.01f;
		[Tooltip("Maximum scale value possible (world space scale)")]
		public float MaxScale = 100f;

		[Tooltip("Used for dead zone for scaling")]
		public float ScaleBuffer = 0.01f;

		[Tooltip("If you don't trust or don't like the auto size calculation, specify a manual size here. 0 is ignored")]
		public float ManualObjectSize = 0;

		public enum ScaleStateEnum
		{
			Static,
			Shrinking,
			Growing
		}

		public ScaleStateEnum ScaleState = ScaleStateEnum.Static;

		// 0 to 1 between MinScale and MaxScale.  If currently < max, then scaling is being applied.
		// This value is subject to inaccuracies due to smoothing/interpolation/momentum
		public float CurrentScalePercent
		{
			get { return objectScalePercent; }
		}

		// 0 to 1 between MinDistance and MaxDistance.  If current < max, object is potentially on a surface [or some other condition like interpolating] (since it may still be on surface, but scale percent may be clamped at max)
		// This value is subject to inaccuracies due to smoothing/interpolation/momentum
		public float CurrentDistancePercent
		{
			get { return objectDistancePercent; }
		}
		#endregion

		#region private members
		private Transform head;
		float fovScalar = 1f;
		float objectSize = 1f;
		float objectScalePercent = 1f;
		float objectDistancePercent = 1f;
		#endregion

		void Start()
		{
			head = Veil.Instance.HeadTransform;

			float baseSize = CalculateObjectSize();

			if (baseSize > 0)
			{
				objectSize = baseSize;
			}
			else
			{
				Debug.LogError("ConstantViewSize: Object base size calculate was 0, defaulting to 1");
				objectSize = 1f;
			}
		}

		/// <summary>
		///   Attempts to calculate the size of the bounds which contains all child renderers.
		///   This may be tricky to use, as this happens during initialization, while the app may
		///   be undergoing scaling by other solvers/components.  Thus, the size calculation might
		///   be inaccurate.  It's probably a better idea to use ManualObjectSize just to be sure.
		/// </summary>
		/// <returns> Object diameter </returns>
		float CalculateObjectSize()
		{
			if (ManualObjectSize > 0)
			{
				return ManualObjectSize;
			}

			Vector3 rootScale = transform.root.localScale;
			transform.root.localScale = Vector3.one;

			float maxSize = 1f;

			Bounds combinedBounds = new Bounds(transform.position, Vector3.zero);
			Renderer[] renderers = this.GetComponentsInChildren<Renderer>();

			foreach (Renderer render in renderers)
			{
				combinedBounds.Encapsulate(render.bounds);
			}

			maxSize = combinedBounds.extents.magnitude;

			transform.root.localScale = rootScale;

			return maxSize;
		}


		public override void SolverUpdate()
		{
			float lastScalePct = objectScalePercent;
			AdjustSizeForView(head);
			float scaleDiff = (objectScalePercent - lastScalePct) / solverHandler.DeltaTime;

			if (scaleDiff > ScaleBuffer)
			{
				ScaleState = ScaleStateEnum.Growing;
			}
			else if (scaleDiff < -ScaleBuffer)
			{
				ScaleState = ScaleStateEnum.Shrinking;
			}
			else
			{
				ScaleState = ScaleStateEnum.Static;
			}
		}

		/// <summary>
		///   Returns the scale to be applied based on the FOV.  This scale will be multiplied by distance as part of
		///   the final scale calculation, so this is the ratio of vertical fov to distance.
		/// </summary>
		/// <returns> Scale of vFOV </returns>
		public float GetFOVScalar()
		{
            float camFOVrad = (Camera.main.aspect * Camera.main.fieldOfView) * Mathf.Deg2Rad;

			float sinfov = Mathf.Sin(camFOVrad / 2f);
			float scalar = 2f * TargetViewPercentV * sinfov / objectSize;

			return scalar;
		}

		// Borrowed from HoloSkype?
		private void AdjustSizeForView(Transform targetTransform)
		{
			if (targetTransform != null)
			{
				// Get current fov each time instead of trying to cache it.  Can never count on init order these days
				fovScalar = GetFOVScalar();

                // Set the linked alt scale ahead of our work.  This is an attempt to minimize jittering by having solvers work with an interpolated scale.
                solverHandler.AltScale.SetGoal(this.transform.localScale);

				// Calculate scale based on distance from view.  Do not interpolate so we can appear at a constant size if possible.  Borrowed from greybox.
				//bool ignoreParent = false;
				float scalePower = 1f;

				Vector3 targetPosition = targetTransform.position;
				float distance = Mathf.Clamp(Vector3.Distance(transform.position, targetPosition), MinDistance, MaxDistance);
				float scale = Mathf.Clamp(fovScalar * Mathf.Pow(distance, scalePower), MinScale, MaxScale);
				this.GoalScale = Vector3.one * scale;

				// Save some state information for external use
				objectDistancePercent = Mathf.InverseLerp(MinDistance, MaxDistance, distance);
				objectScalePercent = Mathf.InverseLerp(MinScale, MaxScale, scale);

				UpdateWorkingScaleToGoal();
			}
		}
	}

}
